﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;
using System.Diagnostics.Contracts;

namespace RgnAnalyzer.Domain
{
	/// <summary>
	/// This class is used to represent object values (just maps of fields to types).
	/// </summary>
	internal class AObject : ALocation
	{
		/// <summary>
		/// True if we know there can be at most one concrete object assocaited with this abstract object.
		/// </summary>
		protected bool m_uniquelocation;

		protected AObject(int address, IRClassType tt, bool uniqueloc, int[] identity, EscapeSiteInfo[] esclocinfo, List<UseModInfo> uml, APtrValueMap fvalues)
			: base(address, identity, esclocinfo, fvalues, uml, tt)
		{ this.m_uniquelocation = uniqueloc; }

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder("Obj " + this.m_loctype.ToString() + " ");
			if(this.IsUniqueLocation())
				sb.Append("@UL ");
			else
				sb.Append("@ML ");

			sb.Append(this.m_ptrinfo.ToString());

			sb.Append(this.ComputeGeneralInfoString());

			return sb.ToString();
		}

		internal override ALocation CopyLocation()
		{
			int[] ida = (this.m_identities == EmptyIdentity ? EmptyIdentity : (int[])this.m_identities.Clone());
			EscapeSiteInfo[] fli = (this.m_escapelines == EmptyAllocLines ? ALocation.EmptyAllocLines : (EscapeSiteInfo[])this.m_escapelines.Clone());
			List<UseModInfo> umv = new List<UseModInfo>(this.m_uml);
			
			return new AObject(this.m_locationaddress, (IRClassType)this.m_loctype, this.m_uniquelocation, ida, fli, umv, this.m_ptrinfo); 
		}

		internal virtual AObject SimulateObjectClone(List<ALocation> memory, int line)
		{
			int newaddress = memory.Count;
			EscapeSiteInfo[] escinfo = new EscapeSiteInfo[] { new EscapeSiteInfo(line, line, line) };
			APtrValueMap ppa = this.m_ptrinfo.SetAllAsInjectiveAsNeededForUniqueLocation(this.LocationType);

			AObject resobj = new AObject(newaddress, (IRClassType)this.m_loctype, true, EmptyIdentity, escinfo, new List<UseModInfo>(), ppa);
			UseModInfo.MarkUMIAtObjectAllocation((IRClassType)resobj.m_loctype, resobj.m_uml, line);

			memory.Add(resobj);
			return resobj;
		}

		/// <summary>
		/// Given a type create the default value for it.
		/// </summary>
		internal static AObject AllocateObjectOfType(List<ALocation> memory, IRClassType tt, int line)
		{
			int address = memory.Count;

			EscapeSiteInfo[] escinfo = new EscapeSiteInfo[] { new EscapeSiteInfo(line, line, line) };

			AObject res = new AObject(address, tt, true, ALocation.EmptyIdentity, escinfo, new List<UseModInfo>(), APtrValueMap.MakeInitalPtrValueMapComposite(tt.TypeDescription.AllTerminalAccessPaths));
			UseModInfo.MarkUMIAtObjectAllocation((IRClassType)res.m_loctype, res.m_uml, line);

			memory.Add(res);
			return res;
		}

		/// <summary>
		/// Return true if this location is ok to strong update.
		/// </summary>
		protected bool DoStrongUpdate(bool uniquetrgtloc)
		{ return uniquetrgtloc & this.m_uniquelocation; }

		/// <summary>
		/// Return the class type that this object is.
		/// </summary>
		internal IRClassType ObjectType
		{ get { return (IRClassType)this.m_loctype; } }

		internal override bool IsUniqueLocation()
		{ return this.m_uniquelocation; }

		internal override void UpdateAsUniqueLocation()
		{
			this.m_uniquelocation = true;
			this.m_ptrinfo = this.m_ptrinfo.SetAllAsInjectiveAsNeededForUniqueLocation(this.LocationType);
		}

		/// <summary>
		/// Set the memory location to represent possibly many concrete objects
		/// </summary>
		internal void MakeMultiLoc()
		{ this.m_uniquelocation = false; }

		/// <summary>
		/// To help simulate some operations mark a use or mod as happening to a ptr/numeric/enum field, but no actual assignment
		/// </summary>
		internal void MarkRTTIUsed(int line)
		{
			IRMemberField rttif = GlobalAnalysisState.TProg.TypeTable.RootObjectRTTIField;
			IRBasicAccessPath rttibap = GlobalAnalysisState.TProg.GetAccessPathForSingleField(rttif);

			UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, rttibap);
		}

		/// <summary>
		/// To help simulate some operations mark a use or mod as happening to a ptr/numeric/enum field, but no actual assignment
		/// </summary>
		internal void MarkLocationUsedModified(IRBasicAccessPath bap, bool use, bool mod, int line)
		{
			UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), use, mod, line, bap);
		}

		/// <summary>
		/// To help simulate some operations mark a use happening to every field in this ptr/numeric/enum field
		/// </summary>
		internal void MarkLocationAllFieldsUsedBI(int line)
		{
			List<IRBasicAccessPath> alltermp = this.m_loctype.TypeDescription.AllTerminalAccessPaths;
			foreach(IRBasicAccessPath bap in alltermp)
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, bap);
		}

		internal override Nullable<APtrValueMap> DoGeneralReadFromLocation(IRBasicAccessPath bap, int line)
		{
			if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, bap);
				return null;
			}
			else if(bap.PathLast.FieldType is IRPtrType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, bap);

				return this.m_ptrinfo.ReadPtrValues(bap);
			}
			else
			{
				foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
				{
					IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, fsbap);
				}

				return this.m_ptrinfo.ReadPtrValues(bap);
			}
		}

		internal override void DoGeneralWriteToLocation(IRBasicAccessPath bap, APtrValueMap pvals, bool isuniquereftrgt, int line)
		{
			if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);
			}
			else if(bap.PathLast.FieldType is IRPtrType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);

				this.m_ptrinfo = this.m_ptrinfo.WritePtrValues(pvals, bap, this.DoStrongUpdate(isuniquereftrgt), this.m_uniquelocation);
			}
			else
			{
				foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
				{
					IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, fsbap);
				}

				this.m_ptrinfo = this.m_ptrinfo.WritePtrValues(pvals, bap, this.DoStrongUpdate(isuniquereftrgt), this.m_uniquelocation);
			}
		}

		internal override IEnumerator<ATrgtEnum> GetTargetEnum()
		{ return this.m_ptrinfo.GetATrgtEnumerator(); }

		internal override int TotalTargetCount()
		{ return this.m_ptrinfo.PTargetCount; }

		/// <summary>
		/// Perform Sanity check on node state.
		/// </summary>
		internal override void CheckNodeOK(List<ALocation> memory)
		{
			base.CheckNodeOK(memory);

			IEnumerator<ATrgtEnum> trgtenum = this.m_ptrinfo.GetATrgtEnumerator();
			while(trgtenum.MoveNext())
			{
				ATrgtEnum tte = trgtenum.Current;

				if(this.m_uniquelocation && (!tte.BAPPath.PathFirst.IsSummaryField) && (!this.ResolvePtrFromEnum(tte).IsInjective))
					DebugReport.ReportError("We have non-injective pointers in a unique memory location??");
			}
		}

		protected override bool EqualALocationSubclass(ALocation ol)
		{
			if(!(ol is AObject))
				return false;

			AObject ob = (AObject)ol;
			Contract.Assert(this.m_loctype == ob.m_loctype, "This needs to be checked first!!");

			if(this.m_uniquelocation != ob.m_uniquelocation)
				return false;

			return APtrValueMap.DomainEqualIgnoreTargets(this.m_ptrinfo, ob.m_ptrinfo);
		}

		internal override void DeletePossibleTargets(HashSet<int> deleteset)
		{ this.m_ptrinfo = this.m_ptrinfo.DeletePossibleTargets(deleteset); }

		internal override void MoveWithUpdatedAddress_ISOMapping(Dictionary<int, int> addrReMap)
		{ 
			APtrValueMap npp = this.m_ptrinfo.UpdateAddresses_ISOMapping(addrReMap);
			this.m_locationaddress = addrReMap[this.m_locationaddress];
			this.m_ptrinfo = npp;
		}

		internal override void MoveWithUpdatedAddresses_MayMergeButNoNewInj(Dictionary<int, int> addrReMap)
		{
			APtrValueMap npp = this.m_ptrinfo.UpdatedAddresses_MayMergeButNoNewInj(addrReMap, this.m_uniquelocation);
			this.m_locationaddress = addrReMap[this.m_locationaddress];
			this.m_ptrinfo = npp;
		}

		protected override ALocation JoinLocationsFromDifferentContextsSubclass(ALocation ol, int[] residentities, EscapeSiteInfo[] resfreshlocs, List<UseModInfo> resuml)
		{
			Contract.Assert(ol is AObject);

			bool stillunique = (this.m_uniquelocation & ((AObject)ol).m_uniquelocation);

			APtrValueMap ppa = APtrValueMap.MergeAPtrMaps(this.m_ptrinfo, ((AObject)ol).m_ptrinfo, true, stillunique);
			return new AObject(this.m_locationaddress, (IRClassType)this.m_loctype, stillunique, residentities, resfreshlocs, resuml, ppa);
		}

		protected override ALocation JoinLocationSetFromSameContextSubclass(List<ALocation> lset, int resaddress, int[] residentities, EscapeSiteInfo[] resfreshlocs, List<UseModInfo> resuml, Dictionary<int, int> addrReMap)
		{
			Nullable<APtrValueMap> acclm = null;

			foreach(ALocation ll in lset)
			{
				if(!acclm.HasValue)
					acclm = ((AObject)ll).m_ptrinfo;
				else
					acclm = APtrValueMap.MergeAPtrMaps(acclm.Value, ((AObject)ll).m_ptrinfo, false, false);	
			}

			acclm = acclm.Value.UpdatedAddresses_MayMergeButNoNewInj(addrReMap, false);
			return new AObject(resaddress, (IRClassType)this.m_loctype, false, residentities, resfreshlocs, resuml, acclm.Value);
		}

		internal override void ReplaceAssociatedCutpointsAsNeeded(Dictionary<int, int> addressToCPIDMap, HashSet<int> idsofinterest)
		{
			if(this.m_ptrinfo.HasTargetInSet(idsofinterest))
				this.m_ptrinfo = this.m_ptrinfo.ReplaceAssociatedCutpointsAsNeeded(addressToCPIDMap);
		}

		internal override void UpdateOutEdgesAfterCutpointEdgeExpand(Dictionary<int, int[]> cpidToAddressMap, HashSet<int> idsofinterest)
		{
			if(this.m_ptrinfo.HasTargetInSet(idsofinterest))
				this.m_ptrinfo = this.m_ptrinfo.UpdateOutEdgesAfterCutpointEdgeExpand(cpidToAddressMap, this.m_uniquelocation);
		}

		/// <summary>
		/// If we have an edge that infeasibly points to escaping objects (but hasn't been updated locally) then we can just delete the target.
		/// </summary>
		internal void ScrubInfeasibleRef(ATrgtEnum trgtenum)
		{ this.m_ptrinfo = this.m_ptrinfo.ScrubInfeasibleRef(trgtenum); }

		/// <summary>
		/// We have discovered this abstract object represents escaping objects that are actually unreachable (dead and will be GC'd).
		/// So forget all this information. If this must be fresh then clear all pointers.
		/// </summary>
		internal bool ElimFreshAllocData()
		{
			if(this.MustBeInternalAlloc())
			{
				this.m_ptrinfo = APtrValueMap.MakeInitalPtrValueMapComposite(this.m_loctype.TypeDescription.AllTerminalAccessPaths);

				return true;
			}
			else
			{
				this.m_uml.RemoveAll((UseModInfo umi) => (umi.UMTag == UseModInfo.Value.UseAsInternal) | (umi.UMTag == UseModInfo.Value.ModAsInternal));
				this.m_escapelines = ALocation.EmptyAllocLines;
				
				return false;
			}
		}

		/// <summary>
		/// Before the final completion pass we want to update output state with unique escape identities
		/// </summary>
		internal void UpdateReturnContextWithEscapeIdentities()
		{
			if(this.m_escapelines == ALocation.EmptyAllocLines)
				return;

			EscapeSiteInfo[] esca = new EscapeSiteInfo[1];
			esca[0] = new EscapeSiteInfo(0, 0, EscapeSiteInfo.EscapeIdentityCounter--);
			this.m_escapelines = esca;
		}
	}
}
